using System;
using System.IO;
using System.Text;
using System.Collections;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;
using System.Reflection;
using System.CodeDom;
using System.CodeDom.Compiler;
using Microsoft.CSharp;
using Microsoft.VisualBasic;

namespace DotNetScriptEngine
{
	/// <summary>
	/// This class will take the code and user preferences and dynamically
	/// generate an assembly.  This assembly will be called via reflection
	/// in order to run the script
	/// </summary>
	internal class AssemblyGenerator : ATrace
	{
		internal Script script;

		internal AssemblyGenerator(string sourceCode)
		{
			script = new Script(sourceCode);
			script.Trace += new TraceEventHandler(TraceException);
		}

		/// <summary>
		/// The actual workhorse of the script engine.  This function will
		/// create the assembly in memmory and return it to be used
		/// </summary>		
		internal Assembly CreateAssembly()
		{
			if (script.SourceCode.Length == 0)
			{
				OnTrace ("Error: There was no CS script code to compile\r\n", null);
				return null;
			}

			//Get the default language object
			DotNetLanguage language = script.Language;

			//Create an instance whichever code provider that is needed
			CodeDomProvider codeProvider = null;
			if (language.languageName == "C#")
				codeProvider = new CSharpCodeProvider();
			else if (language.languageName == "VB")
				codeProvider = new VBCodeProvider();
			else
			{
				//Neither C# or VB is used in the script.  Use reflection to create the language
				//specific code provider object.  This works because all code providers inherit
				//from CodeDomProvider
				codeProvider = LateBindCodeProvider(language);

				//If a code provider cant be created, then throw and exception
				if (codeProvider == null)
					throw new Exception("Could not create Code Provider from " + language.codeProviderName + " from assembly " + language.codeProviderName);
			}

			//create the language specific code compiler
			ICodeCompiler compiler = codeProvider.CreateCompiler();

			//add compiler parameters
			CompilerParameters compilerParams = new CompilerParameters();
			compilerParams.CompilerOptions = "/target:library /optimize";
			compilerParams.GenerateExecutable = false;
			compilerParams.GenerateInMemory = true;			
			compilerParams.IncludeDebugInformation = false;
			compilerParams.ReferencedAssemblies.Add("mscorlib.dll");
			compilerParams.ReferencedAssemblies.Add("System.dll");			

			//add any aditional references needed
			foreach (string dotNetAssembly in script.References)
				compilerParams.ReferencedAssemblies.Add(dotNetAssembly);
						
			//actually compile the code
			CompilerResults results = compiler.CompileAssemblyFromSource(compilerParams, script.SourceCode);

			//Do we have any compiler errors
			if (results.Errors.Count > 0)
			{
				foreach (CompilerError error in results.Errors)
					OnTrace ("Compine Error: " + error.ErrorText + "\r\n", null);

				return null;
			}

			//get a hold of the actual assembly that was generated
			Assembly generatedAssembly = results.CompiledAssembly;

			//return the assembly
			return generatedAssembly;
		}

		/// <summary>
		/// For languages other than VB or C# we have to use reflection to create
		/// an instance of the language specific code provider object
		/// </summary>		
		private CodeDomProvider LateBindCodeProvider(DotNetLanguage language)
		{
			//late load the assembly file that has the desired code provider
			Assembly langAssembly = Assembly.LoadFrom(language.assembly);	
		
			//Create an instance of the code provider
			return (CodeDomProvider)langAssembly.CreateInstance(language.codeProviderName);
		}

		private void TraceException(object sender, TraceEventArgs e)
		{
			OnTrace (e.Trace, e.Ex);
		}
	}
}
